#ifndef _T_BUFFER_2D_GL_H_
#define _T_BUFFER_2D_GL_H_

//////////////////////////////////////////////////////////////////////////
/// T_glBuffer2D is an interface for cuda mappable objects 
/// like host memory or d3d ogl devices
//////////////////////////////////////////////////////////////////////////
template <class T>
class T_Buffer2D_GL : public I_Buffer2D
{
protected:
	int       m_width, m_height, m_pitch;

public:
	T_Buffer2D_GL(void){}
	virtual ~T_Buffer2D_GL(void) 
	{ 
		delete m_pBuffer;
	};

	//////////////////////////////////////////////////////////////////////////
	/// I_Buffer2D knows how to create and release its pointers
	//////////////////////////////////////////////////////////////////////////
	virtual void Create(int32 nWidth, int32 nHeight, int32 nPitch, I_api_Device * pDevice)
	{
		if (!nWidth || !nHeight) return;

		m_width = nWidth;
		m_height = nHeight;
		m_pitch = nPitch;

		int size = (nWidth + nPitch) * nHeight;

		if (m_pBuffer == NULL) m_pBuffer = new T_Buffer_GL<T>();

		m_pBuffer->Create(size, pDevice);
	}

	virtual void Create(int32 nWidth, int32 nHeight, int32 nPitch, I_Buffer * pShared)
	{
		if (!nWidth || !nHeight) return;

		m_width = nWidth;
		m_height = nHeight;
		m_pitch = nPitch;

		int size = (nWidth + nPitch) * nHeight;

		if (m_pBuffer == NULL) m_pBuffer = new T_Buffer_GL<T>();

		m_pBuffer->Create(size, pShared);
	}

	virtual bool Request(int nWidth, int nHeight, int nPitch, int * pOffset, void **hPtr, void **dPtr, void ** bPtr)
	{
		int m_width = nWidth;
		int m_height = nHeight;
		int m_pitch = nPitch;

		int size = (m_width + m_pitch) * m_height;

		return m_pBuffer->Request(size, pOffset, hPtr, dPtr, bPtr);
	}

	virtual void Release(void)
	{
		if (m_pBuffer != NULL && m_pBuffer->Owner()) 
		{
			m_pBuffer->Release();
		}

		m_width = m_height = m_pitch = 0;
	}

	virtual void				Memcpy(int32 nDir, 
														 int32 nStartR = 0, int32 nStartC = 0,
														 int32 nStopR = 0, int32 nStopC = 0)
	{
		if (nStopC == 0) nStopC = m_width-nStartC;
		if (nStopR == 0) nStopR = m_height-nStartR;

		T * hptr = (T *) m_pBuffer->hPtr();
		T * dptr = (T *)m_pBuffer->dPtr();

		switch(nDir)
		{
		case e_Copy_DtoH:
			if (m_pBuffer->Flag() == e_Map_Lock)
			{
				for (int ir = nStartR; ir < nStopR; ir++)
					cudaMemcpy(hptr+ir*(m_width+m_pitch)+nStartC, 
										 dptr+ir*(m_width+m_pitch)+nStartC, 
										 (nStopC - nStartC)*sizeof(T), 
										 cudaMemcpyDeviceToHost);
			} 
			else if (m_pBuffer->Flag() == 0)
			{

				m_pBuffer->bufferPtr()->Map(E_api_Map_Read, 0, (void **) &dptr);

				for (int ir = nStartR; ir < nStopR; ir++)
					memcpy(hptr+ir*(m_width+m_pitch)+nStartC, 
								 dptr+ir*(m_width+m_pitch)+nStartC, 
								 (nStopC - nStartC)*sizeof(T));
				
				m_pBuffer->bufferPtr()->Unmap();
			}
			break;
		case e_Copy_HtoD:
			if (m_pBuffer->Flag() == e_Map_Lock)
			{
				for (int ir = nStartR; ir < nStopR; ir++)
					cudaMemcpy(dptr+ir*(m_width+m_pitch)+nStartC,
										 hptr+ir*(m_width+m_pitch)+nStartC, 
										 (nStopC - nStartC)*sizeof(T), 
										 cudaMemcpyHostToDevice);
			}
			else if (m_pBuffer->Flag() == 0)
			{
				m_pBuffer->bufferPtr()->Map(E_api_Map_Write, 0, (void **) &dptr);

				for (int ir = nStartR; ir < nStopR; ir++)
					memcpy(dptr+ir*(m_width+m_pitch)+nStartC, 
								 hptr+ir*(m_width+m_pitch)+nStartC, 
								 (nStopC - nStartC)*sizeof(T));
				
				m_pBuffer->bufferPtr()->Unmap();
			}
			break;

		default: break;
		}
	}

	virtual void				Memset(void *pElem, 
														 int32 nStartR = 0, int32 nStartC = 0,
														 int32 nStopR = 0, int32 nStopC = 0)
	{
		if (nStopC == 0) nStopC = m_width-nStartC;
		if (nStopR == 0) nStopR = m_height-nStartR;

		T t;

		if (pElem == NULL)
			memset(&t, 0, sizeof(T));
		else
			memcpy(&t, pElem, sizeof(T));

		T * hptr = (T *) m_pBuffer->hPtr();
		T * dptr = (T *) m_pBuffer->dPtr();

		for (int ir = nStartR; ir < nStopR; ir++)
			for (int ic = nStartC; ic < nStopC; ic++)
				hptr[nStartC+ic+ir*(m_width+m_pitch)] = t;


		if (m_pBuffer->Flag() == e_Map_Lock)
		{
			for (int ir = nStartR; ir < nStopR; ir++)
				cudaMemcpy(hptr+ir*(m_width+m_pitch)+nStartC, 
									 dptr+ir*(m_width+m_pitch)+nStartC, 
									 (nStopC - nStartC)*sizeof(T), 
									 cudaMemcpyHostToDevice);
		}
		else if (m_pBuffer->Flag() == 0)
		{
			m_pBuffer->bufferPtr()->Map(E_api_Map_Write, 0, (void **) &dptr);

			for (int ir = nStartR; ir < nStopR; ir++)
				memcpy(dptr+ir*(m_width+m_pitch)+nStartC, 
							 hptr+ir*(m_width+m_pitch)+nStartC, 
							 (nStopC - nStartC)*sizeof(T));

			m_pBuffer->bufferPtr()->Unmap();
		}
	}
};

#endif